/*
 * Copyright 2017 Hannes Dorfmann.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.hannesdorfmann.mosby3.mvp.delegate;

import com.hannesdorfmann.mosby3.MosbySavedState;
import com.hannesdorfmann.mosby3.PresenterManager;
import com.hannesdorfmann.mosby3.mvp.MvpAbility;
import com.hannesdorfmann.mosby3.mvp.MvpPresenter;
import com.hannesdorfmann.mosby3.mvp.MvpView;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilityLifecycleCallbacks;
import ohos.aafwk.ability.AbilitySlice;
import ohos.agp.components.Component;
import ohos.app.Context;
import ohos.app.ElementsCallback;
import ohos.global.configuration.Configuration;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.PacMap;
import ohos.utils.Sequenceable;

import java.util.UUID;

/**
 * The mvp delegate used for everything that derives from {@link Component} like {@link ohos.agp.components.StackLayout}
 * etc.
 *
 * <p>
 * The following methods must be called from the corresponding View lifecycle method:
 * <ul>
 * <li>{@link #onAttachedToWindow()}</li>
 * <li>{@link #onDetachedFromWindow()}</li>
 * </ul>
 * </p>
 *
 */
public class ComponentContainerMvpDelegateImpl<V extends MvpView, P extends MvpPresenter<V>>
    implements ComponentContainerMvpDelegate<V, P>, AbilityLifecycleCallbacks , ElementsCallback {

  private static final String TAG = ComponentContainerMvpDelegateImpl.class.getSimpleName();
  private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, TAG);

  // TODO allow custom save state hook in

  public final static boolean DEBUG = true;
  private static final String DEBUG_TAG = "ViewGroupMvpDelegateImp";

  private ComponentContainerDelegateCallback<V, P> delegateCallback;
  private String mosbyViewId;
  private final boolean keepPresenterDuringScreenOrientationChange;
  private final Ability activity;

  private boolean checkedActivityFinishing = false;
  private boolean presenterDetached = false;
  private boolean presenterDestroyed = false;
  private static final String BASE_URL = "https://github.com/sockeqwe/mosby";
  private static final String ISSURE = "issues";

  public ComponentContainerMvpDelegateImpl(Component view,
      ComponentContainerDelegateCallback<V, P> delegateCallback,
      boolean keepPresenterDuringScreenOrientationChange) {
    if (view == null) {
      throw new NullPointerException("View is null!");
    }

    if (delegateCallback == null) {
      throw new NullPointerException("MvpDelegateCallback is null!");
    }

    this.delegateCallback = delegateCallback;
    this.keepPresenterDuringScreenOrientationChange = keepPresenterDuringScreenOrientationChange;

    this.activity = PresenterManager.getActivity(delegateCallback.getmComponetContext());
    this.activity.getAbilityPackage().registerCallbacks(this, this);

  }

  /**
   * Generates the unique (mosby internal) viewState id and calls {@link
   * MvpDelegateCallback#createPresenter()}
   * to create a new presenter instance
   *
   * @return The new created presenter instance
   */
  protected P createViewIdAndCreatePresenter() {

    P presenter = delegateCallback.createPresenter();
    if (presenter == null) {
      throw new NullPointerException("Presenter returned from createPresenter() is null.");
    }
    if (keepPresenterDuringScreenOrientationChange) {
      Context context = delegateCallback.getmComponetContext();
      mosbyViewId = UUID.randomUUID().toString();
      PresenterManager.putPresenter(PresenterManager.getActivity(context), mosbyViewId, presenter);
    }
    return presenter;
  }

  private Context getContext() {
    Context c = delegateCallback.getmComponetContext();
    if (c == null) {
      throw new NullPointerException("Context returned from " + delegateCallback + " is null");
    }
    return c;
  }

  @Override public void onAttachedToWindow() {
    P presenter = null;
    if (mosbyViewId == null) {
      // No presenter available,
      presenter = createViewIdAndCreatePresenter();
      if (DEBUG) {
        HiLog.error(LABEL, "ComponentContainerMvpDelegateImpl new Presenter instance created: " + presenter);
      }
    } else {
      presenter = PresenterManager.getPresenter(activity, mosbyViewId);
      if (presenter == null) {
        // Process death,
        // hence no presenter with the given viewState id stored, although we have a viewState id
        presenter = createViewIdAndCreatePresenter();
        if (DEBUG) {
          HiLog.error(LABEL, "ComponentContainerMvpDelegateImpl No Presenter instance found in cache, although MosbyView ID present. This was caused by process death, therefore new Presenter instance created: "
                  + presenter);
        }
      } else {
        if (DEBUG) {
          HiLog.error(LABEL, "ComponentContainerMvpDelegateImpl Presenter instance reused from internal cache: " + presenter);
        }
      }
    }

    // presenter is ready, so attach viewState
    V view = delegateCallback.getMvpView();
    if (view == null) {
      throw new NullPointerException(
          "MvpView returned from getMvpView() is null. Returned by " + delegateCallback);
    }

    if (presenter == null) {
      StringBuilder sb = new StringBuilder();
      sb.append(BASE_URL);
      sb.append(ISSURE);
      throw new IllegalStateException(
          "Oops, Presenter is null. This seems to be a Mosby internal bug. Please report this issue here: "+ sb.toString());
    }

    /*
    if (viewStateWillBeRestored) {
      delegateCallback.setRestoringViewState(true);
    }
    */

    delegateCallback.setPresenter(presenter);
    presenter.attachView(view);

    /*
    if (viewStateWillBeRestored) {
      delegateCallback.setRestoringViewState(false);
    }
    */

    if (DEBUG) {
      HiLog.error(LABEL, "ComponentContainerMvpDelegateImpl MvpView attached to Presenter. MvpView: " + view + "   Presenter: " + presenter);
    }
  }

  /**
   * Must be called from {@link View#onSaveInstanceState()}
   */
  public Sequenceable onSaveInstanceState() {

    Sequenceable superState = delegateCallback.superOnSaveInstanceState();
    return superState;
  }

  /**
   * Restore the data from SavedState
   *
   * @param state The state to read data from
   */
  private void restoreSavedState(MosbySavedState state) {

  }

  /**
   * Must be called from {@link View#onRestoreInstanceState(Parcelable)}
   */
  public void onRestoreInstanceState(Sequenceable state) {
    if (!(state instanceof MosbySavedState)) {
      delegateCallback.superOnRestoreInstanceState(state);
      return;
    }

    MosbySavedState savedState = (MosbySavedState) state;
    restoreSavedState(savedState);
  }

  @Override public void onDetachedFromWindow() {

    detachPresenterIfNotDoneYet();

    if (!checkedActivityFinishing) {

      boolean destroyPermanently = !AbilityMvpDelegateImpl.retainPresenterInstance(
          keepPresenterDuringScreenOrientationChange, activity);

      if (destroyPermanently) {
        destroyPresenterIfNotDoneYet();
      } else if (!activity.isUpdatingConfigurations()) {
        // View removed manually from screen
        destroyPresenterIfNotDoneYet();
      }
    }
  }

  private void destroyPresenterIfNotDoneYet() {
    if (!presenterDestroyed) {
      P presenter = delegateCallback.getPresenter();
      if (presenter != null) {
        presenter.destroy();
      }
      presenterDestroyed = true;
      activity.getAbilityPackage().unregisterCallbacks(this, this);
      if (DEBUG) {
        HiLog.error(LABEL, "ComponentContainerMvpDelegateImpl Presenter destroyed: " + presenter);
      }

      if (mosbyViewId != null) {
        PresenterManager.remove(activity, mosbyViewId);
      }
      mosbyViewId = null;
    }
  }

  private void detachPresenterIfNotDoneYet() {
    if (!presenterDetached) {
      P presenter = delegateCallback.getPresenter();
      if (presenter != null) {
        presenter.detachView();
      }
      presenterDetached = true;
      if (DEBUG) {
         HiLog.error(LABEL,
            "view " + delegateCallback.getMvpView() + " detached from Presenter " + presenter);
      }
    }
  }

  @Override
  public void onAbilityStart(Ability ability) {

  }

  @Override
  public void onAbilityActive(Ability ability) {

  }

  @Override
  public void onAbilityInactive(Ability ability) {

  }

  @Override
  public void onAbilityForeground(Ability ability) {

  }

  @Override
  public void onAbilityBackground(Ability ability) {

  }

  @Override
  public void onAbilityStop(Ability ability) {

  }

  @Override
  public void onAbilitySaveState(PacMap pacMap) {

  }

  @Override
  public void onMemoryLevel(int i) {

  }

  @Override
  public void onConfigurationUpdated(Configuration configuration) {

  }

}
